/******************************************************************************
 * 
 * Announce: CSharpKit, Basic algorithms, components and definitions.
 *           Copyright (C) ShenYongchen.
 *           All rights reserved.
 *   Author: 申永辰.郑州 (shenyczz@163.com)
 *  WebSite: http://github.com/shenyczz/CSharpKit
 *
 * THIS CODE IS LICENSED UNDER THE MIT LICENSE (MIT).
 * THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF 
 * ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
 * IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
 * PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
 * 
******************************************************************************/

using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;

namespace CSharpKit.SVM
{
    /// <summary>
    /// Class containing the routines to perform class membership prediction using a trained SVM.
    /// 包含使用经过训练的SVM执行类隶属度预测的例程
    /// </summary>
    public static class Prediction
    {
        /// <summary>
        /// 预测问题中所有向量的类成员。<br/>
        /// Predicts the class memberships of all the vectors in the problem.
        /// </summary>
        /// <param name="problem">The SVM Problem to solve</param>
        /// <param name="outputFile">File for result output</param>
        /// <param name="model">The Model to use</param>
        /// <param name="predict_probability">Whether to output a distribution over the classes(是否在类上输出分布)</param>
        /// <returns>Percentage correctly labelled(标签正确的比率)</returns>
        public static double Predict(SvmProblem problem, string outputFile, SvmModel model, bool predict_probability)
        {
            int total = 0;
            int correct = 0;
            double error = 0;
            double sumv = 0, sumy = 0, sumvv = 0, sumyy = 0, sumvy = 0;

            StreamWriter output = (outputFile != null)
                ? new StreamWriter(outputFile, false, Encoding.Default)
                : null;

            SvmType svm_type = Procedures.svm_get_svm_type(model);
            int nr_class = Procedures.svm_get_nr_class(model);

            int[] labels = new int[nr_class];
            double[] prob_estimates = null;     // 评估

            if (predict_probability)
            {
                if (svm_type == SvmType.EPS_SVR || svm_type == SvmType.NU_SVR)
                {
                    Console.WriteLine("Prob. model for test data: target value = predicted value + z,\nz: Laplace distribution e^(-|z|/sigma)/(2sigma),sigma=" + Procedures.svm_get_svr_probability(model));
                }
                else
                {
                    Procedures.svm_get_labels(model, ref labels);
                    prob_estimates = new double[nr_class];
                    if (output != null)
                    {
                        output.Write("labels");
                        for (int j = 0; j < nr_class; j++)
                        {
                            output.Write(" " + labels[j]);
                        }
                        output.Write("\n");
                    }
                }
            }

            for (int i = 0; i < problem.Count; i++)
            {
                double target = problem.Y[i];   // 分类
                SvmNode[] x = problem.X[i];     // 类向量

                double v;
                if (predict_probability && (svm_type == SvmType.C_SVC || svm_type == SvmType.NU_SVC))
                {
                    v = Procedures.svm_predict_probability(model, x, prob_estimates);
                    if (output != null)
                    {
                        output.Write(v + " ");
                        for (int j = 0; j < nr_class; j++)
                        {
                            output.Write(prob_estimates[j] + " ");
                        }
                        output.Write("\n");
                    }
                }
                else
                {
                    v = Procedures.svm_predict(model, x);
                    if (output != null)
                    {
                        // output.Write(v + "\n");
                        output.Write(v + output.NewLine);
                    }
                }

                if (v == target)
                    ++correct;

                error += (v - target) * (v - target);
                sumv += v;
                sumy += target;
                sumvv += v * v;
                sumyy += target * target;
                sumvy += v * target;
                ++total;
            }

            if (output != null)
                output.Close();

            // 回归返回？其他返回正确比率
            if (model.Parameter.SvmType == SvmType.EPS_SVR || model.Parameter.SvmType == SvmType.NU_SVR)
                return error / total;
            else return (double)correct / total;
        }

        /// <summary>
        /// Predict the labels for all the data points in a problem.
        /// </summary>
        /// <param name="model">The model to use</param>
        /// <param name="problem">The problem to solve</param>
        /// <returns>The predicted labels</returns>
        public static double[] PredictLabels(this SvmModel model, SvmProblem problem)
        {
            return problem.X.Select(o => model.Predict(o)).ToArray();
        }

        /// <summary>
        /// Predict the probability distributions over all labels for each data point in a problem.
        /// </summary>
        /// <param name="model">The model to use</param>
        /// <param name="problem">The problem to solve</param>
        /// <returns>A distribution over labels for each data point</returns>
        public static double[][] PredictLabelsProbability(this SvmModel model, SvmProblem problem)
        {
            return problem.X.Select(o => model.PredictProbability(o)).ToArray();
        }

        /// <summary>
        /// Predict the class for a single input vector.
        /// </summary>
        /// <param name="model">The Model to use for prediction</param>
        /// <param name="x">The vector for which to predict class</param>
        /// <returns>The result</returns>
        public static double Predict(this SvmModel model, SvmNode[] x)
        {
            return Procedures.svm_predict(model, x);
        }

        /// <summary>
        /// Predicts a class distribution for the single input vector.
        /// </summary>
        /// <param name="model">Model to use for prediction</param>
        /// <param name="x">The vector for which to predict the class distribution</param>
        /// <returns>A probability distribtion over classes</returns>
        public static double[] PredictProbability(this SvmModel model, SvmNode[] x)
        {
            SvmType svm_type = Procedures.svm_get_svm_type(model);
            if (svm_type != SvmType.C_SVC && svm_type != SvmType.NU_SVC)
                throw new Exception("Model type " + svm_type + " unable to predict probabilities.");
            int nr_class = Procedures.svm_get_nr_class(model);
            double[] probEstimates = new double[nr_class];
            Procedures.svm_predict_probability(model, x, probEstimates);
            return probEstimates;
        }

        private static void exit_with_help()
        {
            Debug.Write("usage: svm_predict [options] test_file model_file output_file\n" + "options:\n" + "-b probability_estimates: whether to predict probability estimates, 0 or 1 (default 0); one-class SVM not supported yet\n");
            Environment.Exit(1);
        }






        /// <summary>
        /// Legacy method, provided to allow usage as though this were the command line version of libsvm.
        /// </summary>
        /// <param name="args">Standard arguments passed to the svm_predict exectutable.  See libsvm documentation for details.</param>
        [Obsolete("Use the other version of Predict() instead")]
        public static void Predict(params string[] args)
        {
            int i = 0;
            bool predictProbability = false;

            // parse options
            for (i = 0; i < args.Length; i++)
            {
                if (args[i][0] != '-')
                    break;
                ++i;
                switch (args[i - 1][1])
                {

                    case 'b':
                        predictProbability = int.Parse(args[i]) == 1;
                        break;

                    default:
                        throw new ArgumentException("Unknown option");

                }
            }
            if (i >= args.Length)
                throw new ArgumentException("No input, model and output files provided");

            SvmProblem problem = SvmProblem.Read(args[i]);
            SvmModel model = SvmModel.Read(args[i + 1]);
            Predict(problem, args[i + 2], model, predictProbability);
        }

        //}}@@@
    }

}
